﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using DcsGauges.Shared.Network;
using DcsGauges.Shared.DataStructures;
using NLog;
using System.Globalization;

namespace DcsGauges.Shared.Packets
{
    public class PacketBuilder : PacketSerializationBase
    {
        private delegate Packet PacketBuilderDelegate(string packetString);

        private static readonly Logger  log                     = LogManager.GetCurrentClassLogger();

        private Encoding                encoding                = Encoding.UTF8;
        private PacketSerializer        serializer              = new PacketSerializer();

        private static List<Tuple<string, string>> AllTags      = new List<Tuple<string,string>>();
        private static readonly Dictionary<string, PacketBuilderDelegate> packetBuilders            = new Dictionary<string,PacketBuilderDelegate>();

        static PacketBuilder()
        {
            AllTags.Add(GetTag(HandshakePacket.PACKET_NAME));
            AllTags.Add(GetTag(DataPacket.PACKET_NAME));
            AllTags.Add(GetTag(CommandsPacket.PACKET_NAME));

            packetBuilders.Add(HandshakePacket.PACKET_NAME, BuildHandshakePacket);
            packetBuilders.Add(DataPacket.PACKET_NAME,      BuildDataPacket);
            packetBuilders.Add(CommandsPacket.PACKET_NAME,  BuildCommandsPacket);
        }

        public Packet Build(StringBuffer buffer)
        {
            try
            {
                Packet resultPacket                     = null;
                string packetString                     = this.ExtractPacketString(buffer);

                if (packetString != null)
                {
                    foreach (string tagName in packetBuilders.Keys)
                    {
                        string startTag = GetStartTag(tagName);
                        if (packetString.StartsWith(startTag) == true)
                            resultPacket = packetBuilders[tagName](packetString);
                        if (resultPacket != null)
                            return resultPacket;
                    }
                }

                log.Log(LogLevel.Warn, "Packet received cannot be interpreted. Buffer will be cleared. Current buffer is: {0}", buffer.ContentString);
                buffer.Clear();
                return null;
            }
            catch (Exception)
            {
                log.Log(LogLevel.Warn, "Packet received cannot be interpreted. Buffer will be cleared. Current buffer is: {0}", buffer.ContentString);
                buffer.Clear();
                return null;
            }
        }
        
        public byte[] Build(Packet packet)
        {
            byte[] result = this.serializer.Serialize(packet);
            return result;
        }

        private string ExtractPacketString(StringBuffer buffer)
        {
            if(buffer.ContentString.Length <= 0) return null;

            foreach(var tag in AllTags)
            {
                int startTagIndex           = buffer.ContentString.IndexOf(tag.Item1, 0);
                if(startTagIndex < 0)
                    continue; 

                int endTagIndex             = buffer.ContentString.IndexOf(tag.Item2, startTagIndex);

                if(startTagIndex > 0)
                    buffer.Remove(startTagIndex);

                if(startTagIndex >= 0 && endTagIndex > startTagIndex)
                {
                    string packetString     = buffer.ContentString.Substring(startTagIndex, endTagIndex + tag.Item2.Length);
                    buffer.Remove(endTagIndex + tag.Item2.Length);
                    return packetString;
                }
                else
                { 
                    Debug.Assert(false);
                    return null;
                }
            }

            return null;
        }


        private static Packet BuildHandshakePacket(string packetString)
        {
            string[] parts              = PacketParts(HandshakePacket.PACKET_NAME, packetString);
            Debug.Assert(parts.Length == 2);
            string clientTypeString     = parts[0];
            string clientName           = parts[1];

            HandshakePacket.ClientTypes clientType = (HandshakePacket.ClientTypes)Enum.Parse(typeof(HandshakePacket.ClientTypes), clientTypeString);
            return new HandshakePacket(clientType, clientName);
        }

        
        private static Packet BuildDataPacket(string packetString)
        {
            string[] parts              = PacketParts(DataPacket.PACKET_NAME, packetString);
            var values                  = new List<Tuple<DcsProperty,float>>();

            foreach (var p in parts)
            {
                if (string.IsNullOrWhiteSpace(p))
                {
                    continue;
                }

                try
                {
                    string[] propAndValue       = p.Split(DataPacket.ASSIGNMENT_CHAR);

                    string propertyString       = propAndValue[0];
                    string propertyValueString  = propAndValue[1];
                    DcsProperty prop            = int.Parse(propertyString);
                    float value                 = float.Parse(propertyValueString, CultureInfo.InvariantCulture);
                    values.Add(new Tuple<DcsProperty, float>(prop, value));
                }
                catch (Exception exc)
                {
                    log.Log(LogLevel.Debug, exc.ToString());
                    log.Log(LogLevel.Warn, "Could not interpret data packet property [{0}]", p);
                }
            }
            DataPacket packet           = new DataPacket(values);
            return packet;
        }

        private static Packet BuildCommandsPacket(string packetString)
        {
            string[] parts              = PacketParts(CommandsPacket.PACKET_NAME, packetString);
            var commands                = new List<Tuple<DcsProperty, float>>();

            foreach (var p in parts)
            {
                if (string.IsNullOrWhiteSpace(p))
                {
                    continue;
                }

                try
                {
                    string[] propAndValue           = p.Split(CommandsPacket.ASSIGNMENT_CHAR);
                    string propString               = propAndValue[0];
                    string commandArgString         = propAndValue[1];

                    DcsProperty prop                = int.Parse(propString);
                    float commandValue              = float.Parse(commandArgString, CultureInfo.InvariantCulture);
                    commands.Add(new Tuple<DcsProperty, float>(prop, commandValue));
                }
                catch (Exception exc)
                {
                    log.Log(LogLevel.Debug, exc.ToString());
                    log.Log(LogLevel.Warn, "Could not interpret command packet property [{0}]", p);
                }
            }

            CommandsPacket packet                   = new CommandsPacket(commands);
            return packet;
        }

        private static string[] PacketParts(string packetName, string packetString)
        {
            string startTag     = GetStartTag(packetName);
            string endTag       = GetEndTag(packetName);
            packetString        = packetString.Replace(startTag, "").Replace(endTag, "");
            string[] parts      = packetString.Split(SEPERATOR);
            return parts;
        }

    }
}
